﻿/*
    Copyright (c) 2009 Chris Umbel

    Permission is hereby granted, free of charge, to any person obtaining a 
	copy of this software and associated documentation files (the "Software"), 
	to deal in the Software without restriction, including without limitation 
	the rights to use, copy, modify, merge, publish, distribute, sublicense, 
	and/or sell copies of the Software, and to permit persons to whom the Software 
	is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all 
	copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
	FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
	COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 
	IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.ReportingServices.Interfaces;
using Microsoft.ReportingServices.DataProcessing;
using System.Xml;
using System.Text.RegularExpressions;

namespace SimpleDBExtension
{
    public class SimpleDBDataReader : Microsoft.ReportingServices.DataProcessing.IDataReader
    {
#region "Data Members"
        private XmlNamespaceManager _nsman;
        private SimpleDBCommand _command;
        private XmlNode _currentPointer;
        private XmlNode _nextPointer;
        private List<string> _fields = new List<string>();
#endregion

#region "Properties"
        public int FieldCount { get { return _fields.Count; } }
#endregion

#region "Methods"
        public void Dispose() { }

        public string GetName(int i) { return _fields[i]; }

        /* all SimpleDB Fields are string */
        public Type GetFieldType(int i) { 
            return typeof(string); 
        }

        /* get the value of a field */
        public object GetValue(int i) 
        {
            StringBuilder valueBuilder = new StringBuilder();

            if (i == 0)
                /* Name is always the first */
                valueBuilder.Append(_currentPointer.FirstChild.InnerText);
            else
            {
                /* pull the value of the field out of the XML Result.  comma delimit multi valued
                 * attributes */
                foreach(XmlNode valueNode in _currentPointer.SelectNodes(string.Format("x:Attribute[x:Name='{0}']/x:Value", _fields[i]), _nsman)) 
                {
                    /* if there's already at least one value handle the delimitation */
                    if (valueBuilder.Length > 0)
                        valueBuilder.Append(", ");

                    valueBuilder.Append(valueNode.InnerText);
                }
            }

            return valueBuilder.ToString();
        }

        /* get the column position of a field name */
        public int GetOrdinal(string name) 
        { 
            /* because we can't be sure of the schema we must add a node if it doesn't exist */
            if (_fields.Contains<string>(name))
                _fields.Add(name);
            
            return _fields.IndexOf(name); 
        }

        /* initialzie namespace manager and set the current pointer to the first domain node */
        private XmlNode GetFirstDomainNode(XmlDocument results)
        {
            _nsman = new XmlNamespaceManager(results.NameTable);
            _nsman.AddNamespace("x", @"http://sdb.amazonaws.com/doc/2007-11-07/");

            return results.SelectSingleNode("/x:SelectResponse/x:SelectResult", _nsman).FirstChild;            
        }

        /* advance to the next row */
        public bool Read()
        {
            /* advance position */
            _currentPointer = _nextPointer;

            /* if a next token is present in amazon's response issue a request for more data */
            if (_currentPointer != null && _currentPointer.Name.Equals("NextToken"))
                _currentPointer = GetFirstDomainNode(_command.Connection.SendRequest(_command.ExecutableCommandText, 
                    _currentPointer.InnerText));

            /* try to find the next position to advance to */
            if (_currentPointer != null)
                _nextPointer = _currentPointer.NextSibling;

            return (_currentPointer != null);
        }

        /* init field list */
        public void InitFields(XmlNode dataNode)
        {
            /* Add the primary key */
            _fields.Add("Name");

            /* Parse the field names out of all attributes */
            foreach (XmlNode fieldNameNode in dataNode.SelectNodes("//x:Attribute/x:Name", _nsman))
            {
                /* attributes can be duplicated in SimpleDB but we need them to be unique as result columns */
                if (!_fields.Contains(fieldNameNode.InnerText))
                    _fields.Add(fieldNameNode.InnerText);
            }        
        }
#endregion

#region "Constructors"
        public SimpleDBDataReader(SimpleDBCommand command, CommandBehavior behavior)
        {
            _command = command;

            /* if we're just looking for the schema forget the where clause */
            string commandText = behavior == CommandBehavior.SchemaOnly ? 
                Regex.Replace(command.CommandText, @"\sWHERE\s.*", string.Empty, RegexOptions.IgnoreCase) :
                command.ExecutableCommandText;

            /* issue initial command to amazon and set current pointer to first domain node */
            XmlNode domainNode = GetFirstDomainNode(_command.Connection.SendRequest(commandText));

            if (domainNode != null)
            {
                /* init field list based off of first node */
                InitFields(domainNode);
            }

            /* start us off on the first domain node */
            _currentPointer = null;
            _nextPointer = domainNode;                    
        }


        public SimpleDBDataReader(SimpleDBCommand command) : this(command, CommandBehavior.SingleResult)
        {
        }
#endregion
    }
}
